# 使用Python内置GUI模块tkinter
from tkinter import *
# ttk覆盖tkinter部分对象，ttk对tkinter进行了优化
from tkinter.ttk import *
# 深拷贝时需要用到copy模块
import copy
import tkinter.messagebox

# 默认9路
MODE_NUM = 9
NEW_APP = False


# 围棋应用对象定义
class Application(Tk):
    # 初始化棋盘,默认九路棋盘
    def __init__(self, my_mode_num=9):
        Tk.__init__(self)
        # 模式，九路棋：9，十三路棋：13，十九路棋：19
        self.mode_num = my_mode_num
        # 窗口尺寸设置，默认：1.8
        self.size = 1.8
        # 棋盘每格的边长
        self.dd = 360 * self.size / (self.mode_num - 1)
        # 相对九路棋盘的矫正比例
        self.p = 1 if self.mode_num == 9 else (2 / 3 if self.mode_num == 13 else 4 / 9)
        # 定义棋盘阵列,超过边界：-1，无子：0，黑棋：1，白棋：2
        self.positions = [[0 for i in range(self.mode_num + 2)] for i in range(self.mode_num + 2)]
        # 初始化棋盘，所有超过边界的值置-1
        for m in range(self.mode_num + 2):
            for n in range(self.mode_num + 2):
                if m * n == 0 or m == self.mode_num + 1 or n == self.mode_num + 1:
                    self.positions[m][n] = -1
        # 拷贝三份棋盘“快照”，悔棋和判断“打劫”时需要作参考
        self.last_3_positions = copy.deepcopy(self.positions)
        self.last_2_positions = copy.deepcopy(self.positions)
        self.last_1_positions = copy.deepcopy(self.positions)
        # 记录鼠标经过的地方，用于显示shadow时
        self.cross_last = None
        # 当前轮到的玩家，黑：0，白：1，执黑先行
        self.present = 0
        # 设置先手
        self.fm = -1
        # 棋子阴影
        self.cross = None

        # 记录空位置
        self.image_added = None
        self.image_added_sign = None
        # 初始停止运行，点击“开始游戏”运行游戏
        self.stop = True
        # 悔棋次数，次数大于0才可悔棋，初始置0（初始不能悔棋），悔棋后置0，下棋或弃手时恢复为1，以禁止连续悔棋
        self.regret_chance = 0
        # 图片资源，存放在当前目录下的/images/中
        self.image_W = PhotoImage(file="./images/WD-9.png")
        self.image_B = PhotoImage(file="./images/BD-9.png")
        self.image_BD = PhotoImage(file="./images/" + "BD" + "-" + str(self.mode_num) + ".png")
        self.image_WD = PhotoImage(file="./images/" + "WD" + "-" + str(self.mode_num) + ".png")
        self.image_BU = PhotoImage(file="./images/" + "BU" + "-" + str(self.mode_num) + ".png")
        self.image_WU = PhotoImage(file="./images/" + "WU" + "-" + str(self.mode_num) + ".png")
        # 用于黑白棋子图片切换的列表
        self.chequer_wbu_list = [self.image_BU, self.image_WU]
        self.chequer_wbd_list = [self.image_BD, self.image_WD]
        # 窗口大小
        self.geometry(str(int(600 * self.size)) + 'x' + str(int(400 * self.size)))
        # 画布控件，作为容器
        self.canvas_bottom = Canvas(self, bg='#585858', bd=0, width=600 * self.size, height=400 * self.size)
        self.canvas_bottom.place(x=0, y=0)
        # 几个功能按钮
        self.startButton = Button(self, text='开始游戏', command=self.start)
        self.startButton.place(x=480 * self.size, y=200 * self.size)
        self.giveUpButton = Button(self, text='弃一手', command=self.give_up)
        self.giveUpButton.place(x=480 * self.size, y=225 * self.size)
        self.regretButton = Button(self, text='悔棋', command=self.regret_chess)
        self.regretButton.place(x=480 * self.size, y=250 * self.size)
        # 初始悔棋按钮禁用
        self.regretButton['state'] = DISABLED
        self.replayButton = Button(self, text='重新开始', command=self.reload)
        self.replayButton.place(x=480 * self.size, y=275 * self.size)
        self.newGameButton1 = Button(self, text=('十三' if self.mode_num == 9 else '九') + '路棋', command=self.new_game_one)
        self.newGameButton1.place(x=480 * self.size, y=300 * self.size)
        self.newGameButton2 = Button(self, text=('十三' if self.mode_num == 19 else '十九') + '路棋',
                                     command=self.new_game_second)
        self.newGameButton2.place(x=480 * self.size, y=325 * self.size)
        self.quitButton = Button(self, text='退出游戏', command=self.quit)
        self.quitButton.place(x=480 * self.size, y=350 * self.size)
        # 画棋盘，填充颜色
        self.canvas_bottom.create_rectangle(0 * self.size, 0 * self.size, 400 * self.size, 400 * self.size,
                                            fill='#d0892e')
        # 刻画棋盘线及九个点
        # 先画外框粗线
        self.canvas_bottom.create_rectangle(20 * self.size, 20 * self.size, 380 * self.size, 380 * self.size, width=3)
        # 棋盘上的九个定位点，以中点为模型，移动位置，以作出其余八个点
        for m in [-1, 0, 1]:
            for n in [-1, 0, 1]:
                self.original = self.canvas_bottom.create_oval(
                    200 * self.size - self.size * 2,
                    200 * self.size - self.size * 2,
                    200 * self.size + self.size * 2,
                    200 * self.size + self.size * 2, fill='#000')
                self.canvas_bottom.move(
                    self.original,
                    m * self.dd * (2 if self.mode_num == 9 else (3 if self.mode_num == 13 else 6)),
                    n * self.dd * (2 if self.mode_num == 9 else (3 if self.mode_num == 13 else 6)))
        # 画中间的线条
        for i in range(1, self.mode_num - 1):
            self.canvas_bottom.create_line(20 * self.size, 20 * self.size + i * self.dd, 380 * self.size,
                                           20 * self.size + i * self.dd, width=2)
            self.canvas_bottom.create_line(20 * self.size + i * self.dd, 20 * self.size, 20 * self.size + i * self.dd,
                                           380 * self.size, width=2)
        # 放置右侧初始图片
        self.pW = None
        # 默认黑棋先手
        self.pB = self.canvas_bottom.create_image(500 * self.size + 11, 65 * self.size, image=self.image_B)
        # 每张图片都添加image标签，方便reload函数删除图片
        self.canvas_bottom.addtag_withtag('image', self.pB)
        self.bButton = Button(self, text='黑棋先手', command=self.first_b)
        self.bButton.place(x=480 * self.size, y=100 * self.size)
        self.wButton = Button(self, text='白棋先手', command=self.first_w)
        self.wButton.place(x=480 * self.size, y=120 * self.size)
        # 鼠标移动时，调用shadow函数，显示随鼠标移动的棋子
        self.canvas_bottom.bind('<Motion>', self.shadow)
        # 鼠标左键单击时，调用get_down函数，放下棋子
        self.canvas_bottom.bind('<Button-1>', self.get_down)
        # 设置退出快捷键<Ctrl>+<D>，快速退出游戏
        self.bind('<Control-KeyPress-d>', self.keyboard_quit)

    def first_b(self):
        """
        @summary: 黑棋先手
        :return:
        """
        self.present = 0
        self.create_pb()
        self.del_pw()
        if self.stop:
            self.bButton['state'] = DISABLED
            self.wButton['state'] = NORMAL
        else:
            self.bButton['state'] = DISABLED
            self.wButton['state'] = DISABLED

    def first_w(self):
        """
        @summary: 白棋先手
        :return:
        """
        self.present = 1
        self.create_pw()
        self.del_pb()
        if self.stop:
            self.wButton['state'] = DISABLED
            self.bButton['state'] = NORMAL
        else:
            self.bButton['state'] = DISABLED
            self.wButton['state'] = DISABLED

    # 开始游戏函数，点击“开始游戏”时调用
    def start(self):
        # 禁止选先手
        self.bButton['state'] = DISABLED
        self.wButton['state'] = DISABLED
        # 利用右侧图案提示开始时谁先落子
        if self.present == 0:
            self.create_pb()
            self.del_pw()
        else:
            self.create_pw()
            self.del_pb()
        # 开始标志，解除stop
        self.stop = None

    # 放弃一手函数，跳过落子环节
    def give_up(self):
        # 悔棋恢复
        if not self.regret_chance == 1:
            self.regret_chance += 1
        else:
            self.regretButton['state'] = NORMAL
        # 拷贝棋盘状态，记录前三次棋局
        self.last_3_positions = copy.deepcopy(self.last_2_positions)
        self.last_2_positions = copy.deepcopy(self.last_1_positions)
        self.last_1_positions = copy.deepcopy(self.positions)
        self.canvas_bottom.delete('image_added_sign')
        # 轮到下一玩家
        if self.present == 0:
            self.create_pw()
            self.del_pb()
            self.present = 1
        else:
            self.create_pb()
            self.del_pw()
            self.present = 0

    # 悔棋函数，可悔棋一回合，下两回合不可悔棋
    def regret_chess(self):
        # 判定是否可以悔棋，以前第三盘棋局复原棋盘
        if self.regret_chance == 1:
            self.regret_chance = 0
            self.regretButton['state'] = DISABLED
            list_of_b = []
            list_of_w = []
            self.canvas_bottom.delete('image')
            if self.present == 0:
                self.create_pb()
            else:
                self.create_pw()
            for m in range(1, self.mode_num + 1):
                for n in range(1, self.mode_num + 1):
                    self.positions[m][n] = 0
            for m in range(len(self.last_3_positions)):
                for n in range(len(self.last_3_positions[m])):
                    if self.last_3_positions[m][n] == 1:
                        list_of_b += [[n, m]]
                    elif self.last_3_positions[m][n] == 2:
                        list_of_w += [[n, m]]
            self.recover(list_of_b, 0)
            self.recover(list_of_w, 1)
            self.last_1_positions = copy.deepcopy(self.last_3_positions)
            for m in range(1, self.mode_num + 1):
                for n in range(1, self.mode_num + 1):
                    self.last_2_positions[m][n] = 0
                    self.last_3_positions[m][n] = 0

    # 重新加载函数,删除图片，序列归零，设置一些初始参数，点击“重新开始”时调用
    def reload(self):
        if self.stop == 1:
            self.stop = 0
        self.canvas_bottom.delete('image')
        self.regret_chance = 0
        self.present = 0
        self.create_pb()
        for m in range(1, self.mode_num + 1):
            for n in range(1, self.mode_num + 1):
                self.positions[m][n] = 0
                self.last_3_positions[m][n] = 0
                self.last_2_positions[m][n] = 0
                self.last_1_positions[m][n] = 0

    # 以下四个函数实现了右侧太极图的动态创建与删除
    def create_pw(self):
        """
        @summary: 创建白棋
        :return:
        """
        self.pW = self.canvas_bottom.create_image(500 * self.size + 11, 65 * self.size, image=self.image_W)
        self.canvas_bottom.addtag_withtag('image', self.pW)

    def create_pb(self):
        """
        @summary: 创建黑棋
        :return:
        """
        self.pB = self.canvas_bottom.create_image(500 * self.size + 11, 65 * self.size, image=self.image_B)
        self.canvas_bottom.addtag_withtag('image', self.pB)

    def del_pw(self):
        if self.pW:
            self.canvas_bottom.delete(self.pW)

    def del_pb(self):
        if self.pB:
            self.canvas_bottom.delete(self.pB)

    # 显示鼠标移动下棋子的移动
    def shadow(self, event):
        if not self.stop:
            # 找到最近格点，在当前位置靠近的格点出显示棋子图片，并删除上一位置的棋子图片
            if (20 * self.size < event.x < 380 * self.size) and (20 * self.size < event.y < 380 * self.size):
                dx = (event.x - 20 * self.size) % self.dd
                dy = (event.y - 20 * self.size) % self.dd
                self.cross = self.canvas_bottom.create_image(
                    event.x - dx + round(dx / self.dd) * self.dd + 22 * self.p,
                    event.y - dy + round(dy / self.dd) * self.dd - 27 * self.p,
                    image=self.chequer_wbu_list[self.present])
                self.canvas_bottom.addtag_withtag('image', self.cross)
                if self.cross_last is not None:
                    self.canvas_bottom.delete(self.cross_last)
                self.cross_last = self.cross

    # 落子，并驱动玩家的轮流下棋行为
    def get_down(self, event):
        if not self.stop:
            # 先找到最近格点
            if (20 * self.size - self.dd * 0.4 < event.x < self.dd * 0.4 + 380 * self.size) and \
                    (20 * self.size - self.dd * 0.4 < event.y < self.dd * 0.4 + 380 * self.size):
                dx = (event.x - 20 * self.size) % self.dd
                dy = (event.y - 20 * self.size) % self.dd
                x = int((event.x - 20 * self.size - dx) / self.dd + round(dx / self.dd) + 1)
                y = int((event.y - 20 * self.size - dy) / self.dd + round(dy / self.dd) + 1)
                # 判断位置是否已经被占据
                if self.positions[y][x] == 0:
                    # 未被占据，则尝试占据，获得占据后能杀死的棋子列表
                    self.positions[y][x] = self.present + 1
                    self.image_added = self.canvas_bottom.create_image(
                        event.x - dx + round(dx / self.dd) * self.dd + 4 * self.p,
                        event.y - dy + round(dy / self.dd) * self.dd - 5 * self.p,
                        image=self.chequer_wbd_list[self.present])
                    self.canvas_bottom.addtag_withtag('image', self.image_added)
                    # 棋子与位置标签绑定，方便“杀死”
                    self.canvas_bottom.addtag_withtag('position' + str(x) + str(y), self.image_added)
                    dead_list = self.get_dead_list(x, y)
                    self.kill(dead_list)
                    # 判断是否重复棋局
                    if not self.last_2_positions == self.positions:
                        # 判断是否属于有气和杀死对方其中之一
                        if len(dead_list) > 0 or self.if_dead([[x, y]], self.present + 1, [x, y]) == False:
                            # 当不重复棋局，且属于有气和杀死对方其中之一时，落下棋子有效
                            if not self.regret_chance == 1:
                                self.regret_chance += 1
                            else:
                                self.regretButton['state'] = NORMAL
                            self.last_3_positions = copy.deepcopy(self.last_2_positions)
                            self.last_2_positions = copy.deepcopy(self.last_1_positions)
                            self.last_1_positions = copy.deepcopy(self.positions)
                            # 删除上次的标记，重新创建标记
                            self.canvas_bottom.delete('image_added_sign')
                            self.image_added_sign = self.canvas_bottom.create_oval(
                                event.x - dx + round(dx / self.dd) * self.dd + 0.5 * self.dd,
                                event.y - dy + round(dy / self.dd) * self.dd + 0.5 * self.dd,
                                event.x - dx + round(dx / self.dd) * self.dd - 0.5 * self.dd,
                                event.y - dy + round(dy / self.dd) * self.dd - 0.5 * self.dd, width=3, outline='#3ae')
                            self.canvas_bottom.addtag_withtag('image', self.image_added_sign)
                            self.canvas_bottom.addtag_withtag('image_added_sign', self.image_added_sign)
                            if self.present == 0:
                                self.create_pw()
                                self.del_pb()
                                self.present = 1
                            else:
                                self.create_pb()
                                self.del_pw()
                                self.present = 0
                        else:
                            # 不属于杀死对方或有气，则判断为无气，警告并弹出警告框
                            self.positions[y][x] = 0
                            self.canvas_bottom.delete('position' + str(x) + str(y))
                            self.bell()
                            self.show_warning_box('无气', "你被包围了！")
                    else:
                        # 重复棋局，警告打劫
                        self.positions[y][x] = 0
                        self.canvas_bottom.delete('position' + str(x) + str(y))
                        self.recover(dead_list, (1 if self.present == 0 else 0))
                        self.bell()
                        self.show_warning_box("打劫", "此路不通！")
                else:
                    # 覆盖，声音警告
                    self.bell()
            else:
                # 超出边界，声音警告
                self.bell()

    def if_dead(self, dead_list, your_chess, your_position):
        """
        判断棋子（种类为 your_chess，位置为 your_position）是否无气（死亡）。
        如果棋子有气，则返回 False，表示棋子存活。
        如果棋子无气，则返回包含所有无气棋子位置的列表。

        参数：
        - dead_list: 一个列表，初始时包含当前正在检查的棋子的位置。
        - your_chess: 当前正在检查的棋子的种类。
        - your_position: 当前正在检查的棋子的位置。

        返回值：
        - 如果棋子有气，返回 False。
        - 如果棋子无气，返回包含所有无气棋子位置的列表。

        函数逻辑：
        1. 检查当前棋子周围是否有空位，如果有，则棋子有气，返回 False。
        2. 如果周围没有空位，检查周围是否有同类棋子，如果有，则递归调用 if_dead 函数检查这些棋子是否有气。
        3. 如果递归调用返回 False，表示至少有一个同类棋子有气，当前棋子也有气，返回 False。
        4. 如果递归调用返回一个列表，表示所有检查的同类棋子都无气，将这些棋子的位置添加到 dead_list 中。
        5. 如果所有周围的同类棋子都检查完毕且都无气，返回 dead_list，表示当前棋子无气。
        """
        # 检查上下左右四个方向是否有空位
        for i in [-1, 1]:
            # 检查上方和下方
            if [your_position[0] + i, your_position[1]] not in dead_list:
                if self.positions[your_position[1]][your_position[0] + i] == 0:
                    return False  # 如果有空位，当前棋子有气
            # 检查左侧和右侧
            if [your_position[0], your_position[1] + i] not in dead_list:
                if self.positions[your_position[1] + i][your_position[0]] == 0:
                    return False  # 如果有空位，当前棋子有气

        # 检查四个方向上是否有同类棋子，并递归检查这些棋子是否有气
        # 上方的同类棋子
        if ([your_position[0] + 1, your_position[1]] not in dead_list) and (
                self.positions[your_position[1]][your_position[0] + 1] == your_chess):
            mid = self.if_dead(dead_list + [[your_position[0] + 1, your_position[1]]], your_chess,
                               [your_position[0] + 1, your_position[1]])
            if not mid:
                return False  # 如果上方同类棋子有气，则当前棋子也有气
            else:
                dead_list += copy.deepcopy(mid)  # 如果无气，将棋子位置添加到列表中
        # 下方的同类棋子，逻辑同上
        # ...
        # 左侧的同类棋子，逻辑同上
        # ...
        # 右侧的同类棋子，逻辑同上
        # ...

        # 如果所有检查都完成，没有找到有气的同类棋子，则当前棋子无气，返回包含所有无气棋子位置的列表
        return dead_list

    # 警告消息框，接受标题和警告信息
    def show_warning_box(self, title, message):
        self.canvas_bottom.delete(self.cross)
        tkinter.messagebox.showwarning(title, message)

    # 落子后，依次判断四周是否有棋子被杀死，并返回死棋位置列表
    def get_dead_list(self, x, y):
        dead_list = []
        for i in [-1, 1]:
            if self.positions[y][x + i] == (2 if self.present == 0 else 1) and ([x + i, y] not in dead_list):
                kill = self.if_dead([[x + i, y]], (2 if self.present == 0 else 1), [x + i, y])
                if kill:
                    dead_list += copy.deepcopy(kill)
            if self.positions[y + i][x] == (2 if self.present == 0 else 1) and ([x, y + i] not in dead_list):
                kill = self.if_dead([[x, y + i]], (2 if self.present == 0 else 1), [x, y + i])
                if kill:
                    dead_list += copy.deepcopy(kill)
        return dead_list

    # 恢复位置列表list_to_recover为b_or_w指定的棋子
    def recover(self, list_to_recover, b_or_w):
        if len(list_to_recover) > 0:
            for i in range(len(list_to_recover)):
                self.positions[list_to_recover[i][1]][list_to_recover[i][0]] = b_or_w + 1
                self.image_added = self.canvas_bottom.create_image(
                    20 * self.size + (list_to_recover[i][0] - 1) * self.dd + 4 * self.p,
                    20 * self.size + (list_to_recover[i][1] - 1) * self.dd - 5 * self.p,
                    image=self.chequer_wbd_list[b_or_w])
                self.canvas_bottom.addtag_withtag('image', self.image_added)
                self.canvas_bottom.addtag_withtag('position' + str(list_to_recover[i][0]) + str(list_to_recover[i][1]),
                                                  self.image_added)

    # 杀死位置列表killList中的棋子，即删除图片，位置值置0
    def kill(self, kill_list):
        if len(kill_list) > 0:
            for i in range(len(kill_list)):
                self.positions[kill_list[i][1]][kill_list[i][0]] = 0
                self.canvas_bottom.delete('position' + str(kill_list[i][0]) + str(kill_list[i][1]))

    # 键盘快捷键退出游戏
    def keyboard_quit(self, event):
        self.quit()

    # 以下两个函数修改全局变量值，newApp使主函数循环，以建立不同参数的对象
    def new_game_one(self):
        global MODE_NUM, NEW_APP
        MODE_NUM = (13 if self.mode_num == 9 else 9)
        NEW_APP = True
        self.quit()

    def new_game_second(self):
        global MODE_NUM, NEW_APP
        MODE_NUM = (13 if self.mode_num == 19 else 19)
        NEW_APP = True
        self.quit()


# 声明全局变量，用于新建Application对象时切换成不同模式的游戏

def run():
    # 循环，直到不切换游戏模式
    while True:
        app = Application(MODE_NUM)
        app.title('围棋')
        app.mainloop()
        if NEW_APP:
            app.destroy()
        else:
            break


if __name__ == '__main__':
    run()
